package com.zklt.snmp.demo.receiver;

import com.zklt.snmp.demo.config.ServerConfig;
import com.zklt.snmp.demo.service.SnmpService;
import lombok.extern.slf4j.Slf4j;
import org.snmp4j.*;
import org.snmp4j.mp.MPv1;
import org.snmp4j.mp.MPv2c;
import org.snmp4j.mp.MPv3;
import org.snmp4j.security.*;
import org.snmp4j.smi.*;
import org.snmp4j.transport.DefaultTcpTransportMapping;
import org.snmp4j.transport.DefaultUdpTransportMapping;
import org.snmp4j.util.MultiThreadedMessageDispatcher;
import org.snmp4j.util.ThreadPool;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author PoX21s
 * @date 2024/5/8 11:14
 * @apiNote
 */

@Slf4j
@Component
public class SnmpReceiver implements CommandResponder {

    @Resource
    private ServerConfig serverConfig;

    @Resource
    private SnmpService snmpService;

    private Snmp snmp;

    /**
     * 初始化snmp
     *
     * @throws Exception 初始化过程中可能出现的异常
     */
    private void init() throws Exception {
        // 创建线程池
        ThreadPool threadPool = ThreadPool.create(serverConfig.getThreadPoolName(), serverConfig.getThreadPoolSize());
        // 初始化多线程消息转发类
        MultiThreadedMessageDispatcher dispatcher = new MultiThreadedMessageDispatcher(threadPool, new MessageDispatcherImpl());
        // 格式化地址字符串
        String addr = String.format("%s:%s/%s", serverConfig.getProtocol(), serverConfig.getIp(), serverConfig.getPort());
        // 解析地址字符串为Address对象
        // 本地IP与监听端口
        Address listenAddress = GenericAddress.parse(addr); // 本地IP与监听端口
        TransportMapping<?> transport = null;
        // 根据地址类型创建transportMapping
        // 创建transportMapping，对TCP与UDP协议进行处理
        if (listenAddress instanceof UdpAddress) {
            // 如果是UDP协议，则创建UDP传输映射
            transport = new DefaultUdpTransportMapping((UdpAddress) listenAddress);
        } else {
            // 如果是TCP协议，则创建TCP传输映射
            transport = new DefaultTcpTransportMapping((TcpAddress) listenAddress);
        }
        // 创建snmp实例
        // 创建snmp
        snmp = new Snmp(dispatcher, transport);
        // 添加消息处理模型
        // 其中要增加三种处理模型。如果snmp初始化使用的是Snmp(TransportMapping<? extends Address> transportMapping) ,就不需要增加
        snmp.getMessageDispatcher().addMessageProcessingModel(new MPv1());
        snmp.getMessageDispatcher().addMessageProcessingModel(new MPv2c());
        // 配置snmpV3版本的用户
        // 当要支持snmpV3版本时，需要配置user
        USM usm = new USM(SecurityProtocols.getInstance(), new OctetString(MPv3.createLocalEngineID()), 0);
        UsmUser user = new UsmUser(new OctetString(serverConfig.getSecurityName()), AuthMD5.ID, new OctetString(serverConfig.getAuthPass()), PrivDES.ID, new OctetString(serverConfig.getPrivacyPass()));
        usm.addUser(user.getSecurityName(), user);
        // 添加安全模型
        SecurityModels.getInstance().addSecurityModel(usm);
        // 添加snmpV3的消息处理模型
        snmp.getMessageDispatcher().addMessageProcessingModel(new MPv3());
        // 开始监听
        // 监听
        snmp.listen();
    }


    /**
     * 启动进程
     */
    public void run() {
        try {
            // 初始化SNMP接收器
            init();
            // 将当前对象作为命令响应器添加到SNMP中
            snmp.addCommandResponder(this);
            // 输出日志信息，表示开始监听SNMP-Trap信息
            log.info("开始监听SNMP-Trap信息");
        } catch (Exception ex) {
            // 打印异常堆栈信息
            ex.printStackTrace();
        }
    }


    /**
     * 当监听到消息时，会自动调用该方法
     *
     * @param respEvent SNMP消息事件
     */
    @Override
    public void processPdu(CommandResponderEvent respEvent) {
        if (respEvent == null || respEvent.getPDU() == null) {
            log.warn("未监听到消息！");
        } else if (respEvent.getPDU().getType() != PDU.TRAP) {
            log.warn("仅支持Trap消息！");
        } else {
            // 获取消息中的变量绑定列表
            Vector<? extends VariableBinding> recVBs = respEvent.getPDU().getVariableBindings();
            // 输出消息类型信息
            log.info(String.format("Type TRAP   : %s", respEvent.getPDU().getType() == PDU.TRAP));
            // 输出消息来源地址信息
            log.info(String.format("Address     : %s", respEvent.getPeerAddress()));
            // 输出消息共同体信息
            log.info(String.format("Community   : %s", new String(respEvent.getSecurityName())));
            // 输出消息变量信息
            log.info(String.format("Variable    : %s", recVBs));
            // 创建一个并发哈希表，用于存储解析后的SNMP数据
            ConcurrentHashMap<String, Object> map = new ConcurrentHashMap<>();
            // 遍历变量绑定列表，将OID和对应的值存储到哈希表中
            for (int i = 0; i < recVBs.size(); i++) {
                VariableBinding recVB = recVBs.elementAt(i);
                // map.put(recVB.getOid().toString(), recVB.getVariable().toString());
                map.put(recVB.getOid().toString(), recVB.toValueString());
            }
            // 调用snmpService的handlerSnmpData方法，将解析后的SNMP数据传递给该方法进行处理
            snmpService.handlerSnmpData(map);
        }
    }

}
